import { Component, cls, type StyledProps } from '@knno/jsx';
import SVGArrowDown from '@mdi/svg/svg/arrow-down.svg';
import { getOffsetX, rem2px } from '../../tools/utils';
import { defineElement } from '../defineElement';
import { Icon } from '../icon/icon';
import { Loading } from '../loading/loading';
import { pulldownStyle } from './pulldown.css';

const PulldownElement = defineElement('knno-pulldown', HTMLDivElement, 'div');

export type PulldownProps = StyledProps<Pulldown> & {
	mouseScroll?: boolean;
	onScrollBottom?: (this: Pulldown) => void;
	onRefresh?: (this: Pulldown) => Promise<void>;
	onCanRefresh?: (this: Pulldown, isTouch: boolean) => boolean;
};

export class Pulldown extends Component<PulldownProps, Pulldown, HTMLDivElement> {
	protected render(): Node {
		let info: HTMLDivElement;
		let anchor: HTMLDivElement;
		const arrow = (<Icon svg={SVGArrowDown} />) as SVGElement;

		let Y: number;
		let downID: number | null = null;
		let maxH: number;
		// let isTouch = false;
		let ready = false;
		let refreshing = false;
		let top: number | null = null;
		let canRefresh = false;
		let moved = false;

		const move = (pos: number) => {
			if (pos > 0) moved = true;
			this.container.style.transform = `translateY(${pos}px)`;
			info.style.height = `${pos - 1 > 0 ? pos - 1 : 0}px`;
		};

		const moveAni = (pos: number, cb?: () => void) => {
			this.container.style.transition = 'transform 0.2s';
			info.style.transition = 'height 0.2s';
			move(pos);
			setTimeout(() => {
				this.container.style.transition = '';
				info.style.transition = '';
				cb?.();
			}, 200);
		};

		const onCancel = () => {
			downID = null;
			// isTouch = false;
			refreshing = false;
			this.el.classList.remove('refreshing');
			top = null;
			arrow.classList.remove('ready');
			moveAni(0, () => info.replaceChildren(arrow));
		};

		const root = (
			<PulldownElement
				onConnected={() => {
					insObserver.observe(anchor);
				}}
				onDisconnected={() => {
					insObserver.disconnect();
				}}
				class={cls(this.props.class, pulldownStyle)}
				style={this.props.style}
				onContextMenu={(evt) => {
					evt.preventDefault();
				}}
				// onTouchStart={() => {
				// 	isTouch = true;
				// }}
				onPointerDown={(evt) => {
					if (refreshing) {
						return;
					}
					// isTouch = false;
					ready = false;
					let onSide = false;
					if (evt.target) {
						// prevent touch on scrollbar area
						const x = evt.offsetX + getOffsetX(evt.target as HTMLElement, this.el);
						if (x > this.el.offsetWidth - 20) {
							onSide = true;
						}
					}
					if (evt.isPrimary && !onSide) {
						// evt.preventDefault();
						// evt.stopImmediatePropagation();
						Y = evt.clientY;
						downID = evt.pointerId;
						top = this.el.scrollTop;
						canRefresh =
							typeof this.props.onCanRefresh === 'function'
								? this.props.onCanRefresh.call(this, evt.pointerType === 'touch')
								: top === 0;
						maxH = Math.floor(rem2px(1) * 10);
					}
				}}
				onTouchMoveCapture={(evt) => {
					if (evt.touches[0].clientY > Y && canRefresh) {
						if (evt.cancelable) {
							evt.preventDefault();
						}
						evt.stopImmediatePropagation();
					}
				}}
				onClickCapture={(evt) => {
					if (moved) {
						evt.stopImmediatePropagation();
						evt.stopPropagation();
					}
					moved = false;
				}}
				onPointerCancel={onCancel}
				onPointerUp={(evt) => {
					if (downID === null) return;
					if (ready && typeof this.props.onRefresh === 'function') {
						evt.preventDefault();
						evt.stopImmediatePropagation();
						downID = null;
						refreshing = true;
						this.el.classList.add('refreshing');
						info.replaceChildren(<Loading />);
						moveAni(maxH);
						this.props.onRefresh.call(this).finally(() => {
							onCancel();
						});
					} else {
						onCancel();
					}
				}}
				onPointerMoveCapture={(evt) => {
					if (downID != null && downID === evt.pointerId) {
						let h = evt.clientY - 30 - Y;
						if (h > 0) {
							if (canRefresh /*!isTouch || noScroll(evt.target as HTMLElement)*/) {
								this.el.setPointerCapture(downID);
								evt.stopImmediatePropagation();
								evt.preventDefault();
							} else {
								if (this.props.mouseScroll /*&& !isTouch*/ && top != null) {
									this.el.scrollTop = top - (evt.clientY - Y);
								}
								return;
							}
							if (h > maxH) {
								const diff = Math.min(h - maxH, maxH);
								const m = (Math.log(diff) / Math.log(maxH)) * (diff / 2);
								h = Math.floor(maxH + m);
								if (!ready) {
									ready = true;
									arrow.classList.add('ready');
								}
							} else {
								if (ready) {
									ready = false;
									arrow.classList.remove('ready');
								}
							}
						}
						if (h < 0) h = 0;
						if (canRefresh) move(h);
						if (this.props.mouseScroll && /*!isTouch &&*/ top != null) {
							this.el.scrollTop = top - (evt.clientY - Y);
						}
					}
				}}
			>
				<div class="info" ref={(d) => (info = d)}>
					{arrow}
				</div>
				<div class="content">{this.props.children}</div>
				<div class="anchor" ref={(a) => (anchor = a)}></div>
			</PulldownElement>
		) as HTMLDivElement;
		const insObserver = new IntersectionObserver(
			(entries) => {
				if (entries[0].isIntersecting) {
					this.props.onScrollBottom?.call(this);
				}
			},
			{ root },
		);
		return root;
	}

	onRefresh(handler: ((this: Pulldown) => Promise<void>) | undefined): this {
		this.props.onRefresh = handler;
		return this;
	}

	onCanRefresh(handler: ((this: Pulldown) => boolean) | undefined): this {
		this.props.onCanRefresh = handler;
		return this;
	}

	onScrollBottom(handler: ((this: Pulldown) => void) | undefined): this {
		this.props.onScrollBottom = handler;
		return this;
	}

	scrollToBottom(smooth = true): this {
		this.el.scroll({ top: this.el.scrollHeight, behavior: smooth ? 'smooth' : 'instant' });
		return this;
	}

	get mouseScroll(): boolean {
		return this.props.mouseScroll ?? false;
	}
	setMouseScroll(value: boolean): this {
		this.props.mouseScroll = value;
		return this;
	}

	protected get container(): HTMLElement {
		return this.el.querySelector(':scope>div.content')!;
	}
}
